// Copyright (c) 2011 CNRS and LIRIS' Establishments (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org); you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 3 of the License,
// or (at your option) any later version.
//
// Licensees holding a valid commercial license may use this file in
// accordance with the commercial license agreement provided with the software.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//
// $URL$
// $Id$
//
// Author(s)     : Guillaume Damiand <guillaume.damiand@liris.cnrs.fr>
//
#ifndef CGAL_LCC_3_VIEWER_QT_H
#define CGAL_LCC_3_VIEWER_QT_H

#include <QApplication>
#include <QKeyEvent>

#include <QGLViewer/qglviewer.h>
#include <GL/gl.h>
#include <CGAL/Linear_cell_complex.h>
#include <CGAL/Cartesian.h>
#include <CGAL/Cartesian_converter.h>

#include "region_growing.h"

typedef CGAL::Cartesian<double> Local_kernel;
typedef typename Local_kernel::Point_3  Local_point;
typedef typename Local_kernel::Vector_3 Local_vector;

template<class LCC, int dim=LCC::ambient_dimension>
struct Geom_utils;

template<class LCC>
struct Geom_utils<LCC,3>
{
  Local_point get_point(typename LCC::Vertex_attribute_const_handle vh)
  { return converter(vh->point()); }

  Local_point get_point(typename LCC::Dart_const_handle dh)
  { return converter(LCC::point(dh)); }
  
  Local_vector get_facet_normal(LCC& lcc, typename LCC::Dart_const_handle dh)
  {
    Local_vector n = converter(CGAL::compute_normal_of_cell_2<LCC>(lcc,dh));
    n = n/(CGAL::sqrt(n*n));
    return n;
  }

  Local_vector get_vertex_normal(LCC& lcc, typename LCC::Dart_const_handle dh)
  {
    Local_vector n = converter(CGAL::compute_normal_of_cell_0<LCC>(lcc,dh));
    n = n/(CGAL::sqrt(n*n));
    return n;
  }
protected:
  CGAL::Cartesian_converter<typename LCC::Traits, Local_kernel> converter;
};

template<class LCC>
struct Geom_utils<LCC,2>
{
  Local_point get_point(typename LCC::Vertex_attribute_const_handle vh)
  {
    Local_point p(converter(vh->point().x()),0,converter(vh->point().y()));
    return p;
  }

  Local_point get_point(typename LCC::Dart_const_handle dh)
  { return get_point(LCC::vertex_attribute(dh)); }

  Local_vector get_facet_normal(LCC&, typename LCC::Dart_const_handle)
  {
    Local_vector n(0,1,0);
    return n;
  }

  Local_vector get_vertex_normal(LCC&, typename LCC::Dart_const_handle)
  {
    Local_vector n(0,1,0);
    return n;    
  }
protected:
  CGAL::Cartesian_converter<typename LCC::Traits, Local_kernel> converter;  
};

template<class LCC>
CGAL::Bbox_3 bbox(LCC& lcc)
{
  CGAL::Bbox_3 bb;
  Geom_utils<LCC> geomutils;
  
  typename LCC::Vertex_attribute_range::const_iterator
    it=lcc.vertex_attributes().begin(), itend=lcc.vertex_attributes().end();
  if ( it!=itend )
  {
    bb = geomutils.get_point(it).bbox();
    for( ++it; it!=itend; ++it)
    {
      bb = bb + geomutils.get_point(it).bbox();
    }
  }
  
  return bb;
}

template<class LCC>
class SimpleLCCViewerQt : public QGLViewer
{
private:
	typedef typename LCC::Dart_handle Dart_handle;

	LCC& m_lcc;
	RegionGrowing<LCC>& m_rg;
	
	bool wireframe;
	bool flatShading;
	bool edges;
	bool vertices;
	Geom_utils<LCC> geomutils;
	std::vector<Local_vector> m_regionColors;
	
	GLuint m_dlFacet;
	GLuint m_dlFacetFlat;
	GLuint m_dlEdge;
	bool m_displayListCreated;
  
protected:
	Local_vector randomColor(double h)
	{
		double f, p, q, t;
		int i;

		i = floor(h*360.0/60.0);
		f = h*360.0/60.0 - (double) i;
		p = 0;
		q = 1.0 - f;
		t = f;

		switch(i)
		{
			case 0:
				return Local_vector(1, t, p);
			case 1:
				return Local_vector(q, 1, p);
			case 2:
				return Local_vector(p, 1, t);
			case 3:
				return Local_vector(p, q, 1);
			case 4:
				return Local_vector(t, p, 1);
			default:
				return Local_vector(1, p, q);
		}
	}
	
public:
	SimpleLCCViewerQt(RegionGrowing<LCC>& rg) :
		QGLViewer(),
		m_rg(rg),
		m_lcc(rg.lcc()),
        wireframe(false),
        flatShading(false),
        edges(true),
        vertices(true)
	{
		setWindowTitle("3D lcc viewer");
		resize(500, 450);

		QGLFormat newFormat = this->format();
		newFormat.setSampleBuffers(true);
		newFormat.setSamples(16);
		this->setFormat(newFormat);
	
		int nbRegion = m_rg.nbRegion();
		m_regionColors.reserve(nbRegion);

		for(int i=0; i<nbRegion; i++)
		{
			double hue = i/(double)nbRegion;
			m_regionColors.push_back(randomColor(hue));
		}

		m_displayListCreated = false;
	}

protected:
	void drawAllFacet(bool flat)
	{
		for(
			typename LCC::template One_dart_per_cell_range<2>::iterator dartIter=m_lcc.template one_dart_per_cell<2>().begin();
			dartIter.cont();
			++dartIter)
		{
			Dart_handle& dart = dartIter;
			
			::glBegin(GL_POLYGON);
			Local_vector regionColor = randomColor(drand48());
			
			if(m_lcc.is_marked(dart, m_rg.seedMarker()))
			{
				::glColor3d(0.5, 0.5, 0.5);
			}
			else
			{
				int region = dart->template attribute<2>()->info();
				
				if(region >= 0)
				{
					::glColor3d(m_regionColors[region].x(), m_regionColors[region].y(), m_regionColors[region].z());
				}
				else
				{
					::glColor3f(1.0f, 1.0f, 1.0f);
				}
			}
			
			if(flat)
			{
				Local_vector normal = geomutils.get_facet_normal(m_lcc,dart);
				::glNormal3d(normal.x(), normal.y(), normal.z());
			}
			
			for(
				typename LCC::template Dart_of_orbit_range<1>::const_iterator orbitIter = m_lcc.template darts_of_orbit<1>(dart).begin();
				orbitIter != m_lcc.template darts_of_orbit<1>(dart).end();
				++orbitIter)
			{
				if(!flat)
				{
					Local_vector n = geomutils.get_vertex_normal(m_lcc,orbitIter);
					::glNormal3d(n.x(),n.y(),n.z());
				}
				
				Local_point p = geomutils.get_point(orbitIter);
				::glVertex3d(p.x(),p.y(),p.z());
			}
			
			::glEnd();
		}
	}
	
	void drawAllEdge()
	{
		::glBegin(GL_LINES);
		::glColor3f(0.0f, 0.0f, 0.0f);
		
		for(
			typename LCC::template One_dart_per_cell_range<2>::iterator dartIter=m_lcc.template one_dart_per_cell<2>().begin();
			dartIter.cont();
			++dartIter)
		{
			Dart_handle& dart = dartIter;
			
			for(
				typename LCC::template Dart_of_orbit_range<1>::iterator orbitIter = m_lcc.template darts_of_orbit<1>(dart).begin();
				orbitIter != m_lcc.template darts_of_orbit<1>(dart).end();
				++orbitIter)
			{
				int region0 = orbitIter->template attribute<2>()->info();
				int region1 = region0+1; //If no adjacent facet exist, region1 != region0
				
				Dart_handle neiDart = m_lcc.beta(orbitIter, 2);
				if(neiDart != LCC::null_dart_handle)
				{
					region1 = neiDart->template attribute<2>()->info();
				}
				
				if(region0 != region1)
				{
					Local_point p =  geomutils.get_point(orbitIter);
					Dart_handle d2 = orbitIter->other_extremity();
					if(d2 != NULL)
					{
						Local_point p2 = geomutils.get_point(d2);
						glVertex3f(p.x(),p.y(),p.z());
						glVertex3f(p2.x(),p2.y(),p2.z());
					}
				}
			}
		}
			
		::glEnd();
	}
	
	void initDraw()
	{
		//Compile drawFacet
		std::cout << "Compile Display List : Facet" << std::endl;
		m_dlFacet = ::glGenLists(1);
		::glNewList(m_dlFacet, GL_COMPILE);
		drawAllFacet(false);
		::glEndList();
		
		//Compile drawFacet with flat shading
		std::cout << "Compile Display List : Facet (flat shading)" << std::endl;
		m_dlFacetFlat = ::glGenLists(1);
		::glNewList(m_dlFacetFlat, GL_COMPILE);
		drawAllFacet(true);
		::glEndList();
		
		//Compile drawEdge
		std::cout << "Compile Display List : Edge" << std::endl;
		m_dlEdge = ::glGenLists(1);
		::glNewList(m_dlEdge, GL_COMPILE);
		drawAllEdge();
		::glEndList();
		
		m_displayListCreated = true;
	}

	virtual void draw()
	{
		if(!m_displayListCreated) return;
		
		if(flatShading) ::glCallList(m_dlFacetFlat);
		else ::glCallList(m_dlFacet);
		
		if(edges) ::glCallList(m_dlEdge);
	}
  
  virtual void init()
  {
    // Restore previous viewer state.
    restoreStateFromFile();

    // Define 'Control+Q' as the new exit shortcut (default was 'Escape')
    setShortcut(EXIT_VIEWER, Qt::CTRL+Qt::Key_Q);

    // Add custom key description (see keyPressEvent).
    setKeyDescription(Qt::Key_W, "Toggles wire frame display");
    setKeyDescription(Qt::Key_F, "Toggles flat shading display");
    setKeyDescription(Qt::Key_E, "Toggles edges display");
    setKeyDescription(Qt::Key_V, "Toggles vertices display");

    // Light default parameters
    ::glLineWidth(2.4f);
    ::glPointSize(7.f);
    //    ::glEnable(GL_POLYGON_OFFSET_FILL);
    //    ::glPolygonOffset(1.f,1.f);
    ::glClearColor(1.0f,1.0f,1.0f,0.0f);
    ::glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);

    ::glEnable(GL_LIGHTING);
    
    ::glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
    // ::glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);

    if (flatShading)
    {
      ::glShadeModel(GL_FLAT);
      ::glDisable(GL_BLEND); 
      ::glDisable(GL_LINE_SMOOTH); 
      ::glDisable(GL_POLYGON_SMOOTH_HINT); 
      ::glBlendFunc(GL_ONE, GL_ZERO); 
      ::glHint(GL_LINE_SMOOTH_HINT, GL_FASTEST);
    }
    else
    {
      ::glShadeModel(GL_SMOOTH);
      ::glEnable(GL_BLEND);
      ::glEnable(GL_LINE_SMOOTH);
      ::glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
      ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    }

    CGAL::Bbox_3 bb = bbox(m_lcc);
    
    this->camera()->setSceneBoundingBox(qglviewer::Vec(bb.xmin(),
                                                       bb.ymin(),
                                                       bb.zmin()),
                                        qglviewer::Vec(bb.xmax(),
                                                       bb.ymax(),
                                                       bb.zmax()));
    
    this->showEntireScene();
    
    initDraw();
  }
  
  void keyPressEvent(QKeyEvent *e)
  {
    const Qt::KeyboardModifiers modifiers = e->modifiers();

    bool handled = false;
    if ((e->key()==Qt::Key_W) && (modifiers==Qt::NoButton))
    {
      wireframe = !wireframe;
      if (wireframe)
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      else
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      handled = true;
      updateGL();
    }
    else if ((e->key()==Qt::Key_F) && (modifiers==Qt::NoButton))
    {
      flatShading = !flatShading;
      if (flatShading)
      {
        ::glShadeModel(GL_FLAT);
        ::glDisable(GL_BLEND); 
        ::glDisable(GL_LINE_SMOOTH); 
        ::glDisable(GL_POLYGON_SMOOTH_HINT); 
        ::glBlendFunc(GL_ONE, GL_ZERO); 
        ::glHint(GL_LINE_SMOOTH_HINT, GL_FASTEST);
      }
      else
      {
        ::glShadeModel(GL_SMOOTH);
        ::glEnable(GL_BLEND);
        ::glEnable(GL_LINE_SMOOTH);
        ::glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
        ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
      }
      handled = true;
      updateGL();
    }
    else if ((e->key()==Qt::Key_E) && (modifiers==Qt::NoButton))
    {
      edges = !edges;
      handled = true;
      updateGL();
    }
    else if ((e->key()==Qt::Key_V) && (modifiers==Qt::NoButton))
    {
      vertices = !vertices;
      handled = true;
      updateGL();
    }
    
    if (!handled)
      QGLViewer::keyPressEvent(e);
  }


  virtual QString helpString() const
  {
    QString text("<h2>L C C   V i e w e r</h2>");
    text += "Use the mouse to move the camera around the object. ";
    text += "You can respectively revolve around, zoom and translate with "
      "the three mouse buttons. ";
    text += "Left and middle buttons pressed together rotate around the "
      "camera view direction axis<br><br>";
    text += "Pressing <b>Alt</b> and one of the function keys "
      "(<b>F1</b>..<b>F12</b>) defines a camera keyFrame. ";
    text += "Simply press the function key again to restore it. "
      "Several keyFrames define a ";
    text += "camera path. Paths are saved when you quit the application "
      "and restored at next start.<br><br>";
    text += "Press <b>F</b> to display the frame rate, <b>A</b> for the "
      "world axis, ";
    text += "<b>Alt+Return</b> for full screen mode and <b>Control+S</b> "
      "to save a snapshot. ";
    text += "See the <b>Keyboard</b> tab in this window for a complete "
      "shortcut list.<br><br>";
    text += "Double clicks automates single click actions: A left button "
      "double click aligns the closer axis with the camera (if close enough). ";
    text += "A middle button double click fits the zoom of the camera and "
      "the right button re-centers the scene.<br><br>";
    text += "A left button double click while holding right button pressed "
      "defines the camera <i>Revolve Around Point</i>. ";
    text += "See the <b>Mouse</b> tab and the documentation web pages for "
      "details.<br><br>";
    text += "Press <b>Escape</b> to exit the viewer.";
    return text;
  }
};

template<class LCC>
void display_region_growing(RegionGrowing<LCC>& rg)
{
  int argc=1;
  typedef char* s;
  
  const char* argv[2]={"lccviewer","\0"};
  QApplication app(argc,const_cast<char**>(argv));
  
  SimpleLCCViewerQt<LCC> mainwindow(rg);
  mainwindow.show();

  app.exec();
};

#endif // CGAL_LCC_3_VIEWER_QT_H
